package wortubung.util;

import java.util.Map;

public class Transliterator {

	public static abstract class Consumer {
		public final boolean consume(Transliterator t, char lookahead, char result) {
			Character mylookahead = t.lookahead();
			if (mylookahead == null || mylookahead != lookahead)
				return false;
			t.append(result);
			t.forward(2);
			return true;
		}
		public final void consume(Transliterator t, char result) {
			t.append(result);
			t.forward(1);
		}

		public final char charAt(Transliterator t) throws TransliterateException { return t.charAt(); }
		public final boolean isAtWordEnd(Transliterator t) { return t.isAtWordEnd(); }
		public final void forward(Transliterator t, int delta) { t.forward(delta); }
		public final Character lookahead(Transliterator t) { return t.lookahead(); }
		public final String lookahead(Transliterator t, int delta) { return t.lookahead(delta); }
		public final void invalidChar(Transliterator t) throws TransliterateException { t.invalidChar(); }

		public abstract void proceed(Transliterator t) throws TransliterateException;
	}

	public static class OneToOneConsumer extends Consumer {
		private char result;
		public OneToOneConsumer(char result) { this.result = result; }
		public void proceed(Transliterator t) { consume(t, result); }
	}

	/**
	 * @param latinInput an ASCII string representing the transliteration of a vernacular text.
	 */
	public Transliterator(Map<Character,Consumer> toVernacularMap) {
		this.toVernacularMap = toVernacularMap;
	}

	/**
	 * Assuming the input is the Latin transliteration, return the vernacular text.
	 * @throws TransliterateException 
	 */
	public String getVernacular(String latinInput) throws TransliterateException {
		this.latinInput = latinInput;

		ptr = 0;
		sb.setLength(0);
		while (ptr < length()) {
			char ch = charAt();
			if (ch == '\\') {
				if (++ptr < length()) {
					ch = charAt();
					if (ch != '\\') {
						sb.append(charAt());
						++ptr;
					}
					else { // quoted string
						++ptr;
						int idx = latinInput.indexOf("\\\\", ptr);
						if (idx < 0) {
							sb.append(latinInput.substring(ptr));
							ptr = latinInput.length();
						} else {
							sb.append(latinInput.substring(ptr, idx));
							ptr = idx + 2;
						}
					}
				}
			}
			else {
				Consumer c = toVernacularMap.get(ch);
				if (c == null) {
					sb.append(ch);
					++ptr;
				} else {
					c.proceed(this);
				}
			}
		}
		String ret = sb.toString();
		sb.setLength(0);
		return ret;
	}

	/////////////////////////////////////////////////
	// For worker elements to use
	
    private Map<Character,Consumer> toVernacularMap;

	private transient String latinInput;
	private transient int ptr;
	private transient StringBuilder sb = new StringBuilder();

	final int length() { return latinInput == null ? 0 : latinInput.length(); }
	final int ptr() { return ptr; }
	final void append(char c) { sb.append(c); }
	final void appendAndForward(char c) { sb.append(c); forward(1); }
	final void invalidChar() throws TransliterateException {
		throw new TransliterateException("Invalid character for '" + latinInput + "' at #" + ptr() + '.');
	}

	final boolean isAtWordEnd() {
		Character la = lookahead();
		return (la == null) || Character.isWhitespace(la);
	}

	final char charAt() throws TransliterateException {
		try {
			return (char)latinInput.charAt(ptr);
		} catch(Exception e) {
			throw new TransliterateException(
					(latinInput == null) ? "No input." : ("Unexpected ending for '" + latinInput + "'."));
		}
	}

	final void forward(int delta) {
		this.ptr += delta;
	}

	final Character lookahead() {
		if (ptr >= length() - 1)
			return null;
		return latinInput.charAt(ptr+1);
	}

	final String lookahead(int delta) {
		if (delta <= 0 || ptr + delta > latinInput.length())
			return null;
		return latinInput.substring(ptr, ptr + delta);
	}
}
